package de.mmis.core.base.abstracttree.serializer.internal;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import de.mmis.core.base.DeserializableAnnotations.ConstructorDeserializable;
import de.mmis.core.base.Position;
import de.mmis.core.base.abstracttree.serializer.Serializable;
import de.mmis.core.base.abstracttree.serializer.Serializer;
import de.mmis.core.base.gclass.GArray;
import de.mmis.core.base.gclass.GClass;
import de.mmis.core.base.gclass.GParameterized;

/**
 * Class providing an mapping from classes to serializers.
 * 
 * @author Christian Koch
 */
public class ClassSerializerMap {
	private static Map<Class<?>, Serializer<?>> class2serializer;
	static {
		Serializer<?> temp;
		class2serializer = Collections
				.synchronizedMap(new Hashtable<Class<?>, Serializer<?>>());

		temp = new NumberSerializer();
		class2serializer.put(Long.class, temp);
		class2serializer.put(long.class, temp);
		class2serializer.put(Integer.class, temp);
		class2serializer.put(int.class, temp);
		class2serializer.put(Short.class, temp);
		class2serializer.put(short.class, temp);
		class2serializer.put(Byte.class, temp);
		class2serializer.put(byte.class, temp);
		class2serializer.put(Float.class, temp);
		class2serializer.put(float.class, temp);
		class2serializer.put(Double.class, temp);
		class2serializer.put(double.class, temp);

		temp = new BooleanSerializer();
		class2serializer.put(Boolean.class, temp);
		class2serializer.put(boolean.class, temp);

		temp = new CharacterSerializer();
		class2serializer.put(Character.class, temp);
		class2serializer.put(char.class, temp);

		temp = new StringSerializer();
		class2serializer.put(String.class, temp);

		temp = new PositionSerializer();
		class2serializer.put(Position.class, temp);

		temp = new GClassSerializer();
		class2serializer.put(GClass.class, temp);
		class2serializer.put(GParameterized.class, temp);
		class2serializer.put(GArray.class, temp);

		temp = new ClassSerializer();
		class2serializer.put(Class.class, temp);

		// add default serialisers
		temp = new ByteArraySerializer();
		class2serializer.put(byte[].class, temp);

		class2serializer.put(Serializable.class, new SerializableSerializer());

		class2serializer.put(Iterable.class, new IterableSerializer());

		temp = new ArraySerializer();
		class2serializer.put(Object[].class, temp);

		temp = new EnumSerializer();
		class2serializer.put(Enum.class, temp);

		temp = new MapSerializer();
		class2serializer.put(Map.class, temp);

		temp = new ProxySerializer();
		class2serializer.put(Proxy.class, temp);

		temp = new ThrowableSerializer();
		class2serializer.put(Throwable.class, temp);

		temp = new ConstructorDeserializableSerializer();
		class2serializer.put(ConstructorDeserializable.class, temp);

		temp = new ObjectSerializer();
		class2serializer.put(Object.class, temp);
	}

	/**
	 * adds new serializer to serializer map allowing to serialize objects with
	 * is when the class is specified explicitly
	 * 
	 * @param clazz
	 *            class to use serializer for
	 * @param ser
	 *            serializer to use
	 */
	public static void addSerializer(Class<?> clazz, Serializer<?> ser) {
		class2serializer.put(clazz, ser);
	}

	private static synchronized void getSerializerFromResourceFile(
			Class<?> baseclass, ClassLoader classLoader) {

		// get property file resource containing the name of the class that
		// should be used
		InputStream instream = classLoader
				.getResourceAsStream("de/mmis/core/base/abstracttree/serializer/"
						+ baseclass.getName());
		if (instream == null)
			return;

		// read properties file
		Properties p = new Properties();
		try {
			p.load(instream);
		} catch (IOException e) {
			return;
		}

		// get (any) class name in this property map
		Set<String> keys = p.stringPropertyNames();
		if (keys.isEmpty())
			return;
		String classname = keys.iterator().next();

		// load class from class name
		Class<?> clazz = null;
		try {
			clazz = Class.forName(classname, false, classLoader);
		} catch (ClassNotFoundException e) {
			return;
		}

		if (!Serializer.class.isAssignableFrom(clazz))
			return;

		try {
			class2serializer
					.put(baseclass, (Serializer<?>) clazz.newInstance());
		} catch (InstantiationException e) {
			return;
		} catch (IllegalAccessException e) {
			return;
		}

		return;

	}

	/**
	 * gets Serializer for given class using (ordered by priority):<br>
	 * 1. The Serializable interface if implemented<br>
	 * 2. The Serializer mapped to given class if such mapping exists<br>
	 * 3. The generic array, enumeration, iterable or map Serializer if the
	 * class if of the corresponding type 4. The default object Serializer in
	 * all other cases
	 * 
	 * @param baseclass
	 *            class to find a Serializer for
	 * @return found Serializer
	 */
	public static Serializer<?> getSerialiser(Class<?> baseclass,
			ClassLoader classLoader) {

		if (Proxy.class.isAssignableFrom(baseclass)
				&& class2serializer.containsKey(Proxy.class))
			return class2serializer.get(Proxy.class);

		if (baseclass.getAnnotation(ConstructorDeserializable.class) != null
				&& class2serializer
						.containsKey(ConstructorDeserializable.class))
			return class2serializer.get(ConstructorDeserializable.class);

		if (Serializable.class.isAssignableFrom(baseclass)
				&& class2serializer.containsKey(Serializable.class))
			return class2serializer.get(Serializable.class);

		if (!class2serializer.containsKey(baseclass))
			getSerializerFromResourceFile(baseclass, classLoader);
		if (class2serializer.containsKey(baseclass))
			return class2serializer.get(baseclass);

		if (baseclass.isArray() && class2serializer.containsKey(Object[].class))
			return class2serializer.get(Object[].class);

		if (baseclass.isEnum() && class2serializer.containsKey(Enum.class))
			return class2serializer.get(Enum.class);

		if (Iterable.class.isAssignableFrom(baseclass)
				&& class2serializer.containsKey(Iterable.class))
			return class2serializer.get(Iterable.class);

		if (Map.class.isAssignableFrom(baseclass)
				&& class2serializer.containsKey(Map.class))
			return class2serializer.get(Map.class);

		if (Throwable.class.isAssignableFrom(baseclass)
				&& class2serializer.containsKey(Throwable.class))
			return class2serializer.get(Throwable.class);

		if (Class.class.isAssignableFrom(baseclass)
				&& class2serializer.containsKey(Class.class))
			return class2serializer.get(Class.class);

		return class2serializer.get(Object.class);
	}
}
